//-----------------------------------------------------------------------
// <copyright file="Collection.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//-----------------------------------------------------------------------

namespace Microsoft.Live.Messenger
{
    using System;
    using System.Collections;
    using System.Collections.ObjectModel;
    using System.Collections.Specialized;
    using System.ComponentModel;
    using System.Diagnostics;
    using Microsoft.Live.Core;

    /// <summary>
    /// Represents a generic collection.
    /// </summary>
    public abstract class Collection :
        IEnumerable,
        INotifyCollectionChanged,
        INotifyPropertyChanged
    {
        private readonly MessengerEntity entity;
        private readonly User user;

        private readonly Collection<object> collection = new Collection<object>();

        internal Collection(object obj, User user)
        {
            this.entity = new MessengerEntity(obj);
            this.entity.AttachEvent("CollectionChanged", this.OnCollectionChanged);
            this.entity.AttachEvent("PropertyChanged", this.OnPropertyChanged);

            this.user = user;

            this.Populate();
        }

        internal MessengerEntity Entity
        {
            get { return this.entity; }
        }

        internal User User
        {
            get { return this.user; }
        }

        internal abstract object FindOrCreate(MessengerEntity entity);

        internal object GetItem(int index)
        {
            return this.collection[index];
        }

        private void OnAdded(MessengerEntity newEntity, int newStartingIndex)
        {
            object item = this.FindOrCreate(newEntity);
            if (item != null)
            {
                this.collection.Add(item);

                this.OnCollectionChanged(
                    new NotifyCollectionChangedEventArgs(
                        NotifyCollectionChangedAction.Add,
                        item,
                        newStartingIndex));
            }
        }

        private void OnRemoved(MessengerEntity oldEntity, int oldStartingIndex)
        {
            object item = this.FindOrCreate(oldEntity);
            if (item != null)
            {
                this.collection.Remove(item);

                this.OnCollectionChanged(
                    new NotifyCollectionChangedEventArgs(
                        NotifyCollectionChangedAction.Remove,
                        item,
                        oldStartingIndex));
            }
        }

        private void OnReplaced(MessengerEntity oldEntity, MessengerEntity newEntity, int startingIndex)
        {
            object oldItem = this.FindOrCreate(oldEntity);

            Debug.Assert(oldItem != null);

            if (oldItem != null)
            {
                this.collection.Remove(oldItem);

                object newItem = this.FindOrCreate(newEntity);

                this.collection.Add(newItem);

                this.OnCollectionChanged(
                    new NotifyCollectionChangedEventArgs(
                        NotifyCollectionChangedAction.Replace,
                        newItem,
                        oldItem,
                        startingIndex));
            }
        }

        private void Populate()
        {
            MessengerEntity enumerator = new MessengerEntity(this.entity.Invoke("GetEnumerator"));

            while ((bool)enumerator.Invoke("MoveNext"))
            {
                MessengerEntity current = new MessengerEntity(enumerator.GetProperty("Current"));

                this.collection.Add(this.FindOrCreate(current));
            }
        }

        private void OnReset()
        {
            this.collection.Clear();

            this.Populate();

            this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        }

        /// <summary>
        /// Occurs when the collection changes.
        /// </summary>
        public event NotifyCollectionChangedEventHandler CollectionChanged;

        /// <summary>
        /// Occurs when a property changes.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        private void OnCollectionChanged(object sender, MessengerEventArgs e)
        {
            var action = e.Entity.GetEnumProperty<NotifyCollectionChangedAction>("Action");

            switch (action)
            {
                case NotifyCollectionChangedAction.Reset:
                    this.OnReset();
                    break;

                case NotifyCollectionChangedAction.Add:
                {
                    MessengerEntity newItems = new MessengerEntity(e.Entity.GetProperty("NewItems"));
                    MessengerEntity newItem = new MessengerEntity(newItems.Invoke("GetItem", 0));
                    int newStartingIndex = e.Entity.GetNumberProperty<int>("NewStartingIndex");

                    this.OnAdded(newItem, newStartingIndex);
                    break;
                }
                case NotifyCollectionChangedAction.Remove:
                {
                    MessengerEntity oldItems = new MessengerEntity(e.Entity.GetProperty("OldItems"));
                    MessengerEntity oldItem = new MessengerEntity(oldItems.Invoke("GetItem", 0));
                    int oldStartingIndex = e.Entity.GetNumberProperty<int>("OldStartingIndex");

                    this.OnRemoved(oldItem, oldStartingIndex);
                    break;
                }
                case NotifyCollectionChangedAction.Replace:
                {
                    MessengerEntity oldItems = new MessengerEntity(e.Entity.GetProperty("OldItems"));
                    MessengerEntity oldItem = new MessengerEntity(oldItems.Invoke("GetItem", 0));

                    MessengerEntity newItems = new MessengerEntity(e.Entity.GetProperty("NewItems"));
                    MessengerEntity newItem = new MessengerEntity(newItems.Invoke("GetItem", 0));

                    int startingIndex = e.Entity.GetNumberProperty<int>("NewStartingIndex");

                    this.OnReplaced(oldItem, newItem, startingIndex);
                    break;
                }
                default:
                    Debug.Assert(false, "Unexpected NotifyCollectionChangedAction.");
                    return;
            }
        }

        /// <summary>
        /// Raises the <see cref="CollectionChanged"/> event.
        /// </summary>
        /// <param name="e">The event data.</param>
        protected virtual void OnCollectionChanged(
            NotifyCollectionChangedEventArgs e)
        {
            if (this.CollectionChanged != null)
            {
                this.CollectionChanged(this, e);
            }
        }

        /// <summary>
        /// Raises the <see cref="PropertyChanged"/> event.
        /// </summary>
        /// <param name="e">The event data.</param>
        protected virtual void OnPropertyChanged(
            PropertyChangedEventArgs e)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, e);
            }
        }

        /// <summary>
        /// Gets the number of items in the collection.
        /// </summary>
        public int Count
        {
            get { return this.Entity.GetNumberProperty<int>("Count"); }
        }

        /// <summary>
        /// Determines whether the object is within the collection.
        /// </summary>
        /// <param name="obj">The object.</param>
        /// <returns></returns>
        protected internal bool Contains(object obj)
        {
            return this.collection.Contains(obj);
        }

        #region IEnumerable Members

        /// <summary>
        /// Gets the enumerator.
        /// </summary>
        /// <returns></returns>
        public IEnumerator GetEnumerator()
        {
            return this.collection.GetEnumerator();
        }

        #endregion

        private void OnPropertyChanged(object sender, MessengerEventArgs e)
        {
            this.OnPropertyChanged(
                new PropertyChangedEventArgs(
                    e.Entity.GetProperty<string>("PropertyName")));
        }
    }
}